﻿using System;
using System.Runtime.InteropServices;
//using System.IO;
using System.Threading;

namespace EnttecOpenDMX
{
    public class OpenDMX
    {
        public static uint handle;
        public static bool done = false;
        public static int bytesWritten = 0;
        public static FT_STATUS status;
        private static int channelsToOutput = 512;
        private static UInt32 numDevices;
        public static byte[] buffer = new byte[channelsToOutput + 1];

        public const byte BITS_8 = 8;
        public const byte STOP_BITS_2 = 2;
        public const byte PARITY_NONE = 0;
        public const UInt16 FLOW_NONE = 0;
        public const byte PURGE_RX = 1;
        public const byte PURGE_TX = 2;

        private static Thread thread;

        //UCHAR Unsigned char (1 byte)
        //PUCHAR Pointer to unsigned char (4 bytes) 
        //PCHAR Pointer to char (4 bytes) 
        //DWORD Unsigned long (4 bytes) 
        //LPDWORD Pointer to unsigned long (4 bytes) 
        //FT_HANDLE DWORD

        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_CreateDeviceInfoList(ref UInt32 lpdwNumDevs);
        //[DllImport("FTD2XX.dll")]
        //private static extern FT_STATUS FT_GetDeviceInfoList(ref FT_DEVICE_LIST_INFO_NODE pDest, ref UInt32 lpdwNumDevs);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_GetDeviceInfoDetail(UInt32 dwIndex, ref UInt32 lpdwFlags, ref UInt32 lpdwType,
                                                                ref UInt32 lpdwID, ref UInt32 lpdwLocId, ref byte pcSerialNumber,
                                                                ref byte pcDescription, ref UInt32 ftHandle);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_Open(UInt32 uiPort, ref uint ftHandle);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_Close(uint ftHandle);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_Read(uint ftHandle, IntPtr lpBuffer, UInt32 dwBytesToRead, ref UInt32 lpdwBytesReturned);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_Write(uint ftHandle, IntPtr lpBuffer, UInt32 dwBytesToRead, ref UInt32 lpdwBytesWritten);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_SetDataCharacteristics(uint ftHandle, byte uWordLength, byte uStopBits, byte uParity);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_SetFlowControl(uint ftHandle, char usFlowControl, byte uXon, byte uXoff);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_GetModemStatus(uint ftHandle, ref UInt32 lpdwModemStatus);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_Purge(uint ftHandle, UInt32 dwMask);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_ClrRts(uint ftHandle);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_SetBreakOn(uint ftHandle);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_SetBreakOff(uint ftHandle);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_GetStatus(uint ftHandle, ref UInt32 lpdwAmountInRxQueue, ref UInt32 lpdwAmountInTxQueue, ref UInt32 lpdwEventStatus);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_ResetDevice(uint ftHandle);
        [DllImport("FTD2XX.dll")]
        private static extern FT_STATUS FT_SetDivisor(uint ftHandle, char usDivisor);

        //GetDeviceCount
        //GetDeviceSerial(Index)
        //OpenDevice(Index)
        //CloseDevice
        //SelectDevice
        //Open
        //Close
        //SetRate
        //SetChannel
        //SetBuffer
        //SetStartCode
        //Start
        //Stop
        //SetNumChannels

        /* Constructor for the DMX interface
         * Sets up start code of universe
         */
        OpenDMX()
        {
            buffer[0] = 0;  //Set DMX Start Code
        }

        /* getNumDevices(void)
         * Returns the number of devices attached to the computer
         */
        public static UInt32 getNumDevices()
        {
            FT_CreateDeviceInfoList(ref numDevices);
            return numDevices;
        }

        /* open()
         * Opens the first dongle the software finds.
         */
        public static void open()
        {
            handle = 0;
            status = FT_Open(0, ref handle);
            //thread = new Thread(new ThreadStart(writeData));
            //thread.Start();
        }

        /* start()
         * Creates and starts thread to send data out the adapter.
         */
        public static void start()
        {
            if (thread != null)
            {
                thread.Abort();
            }
            thread = new Thread(new ThreadStart(writeData));
            thread.Start();
        }

        /* stop()
         * Kills the thread that is outputting data
         */
        public static void stop()
        {
            thread.Abort();
            thread = null;
        }

        /* close()
         * Closes the port used to send data
         */
        public static void close()
        {
            status = FT_Close(handle);
            handle = 0;
        }

        /* setChannel(int channel, byte value)
         * Sets chanel to value input
         */
        public static void setChannel(int channel, byte value)
        {
            if (channel > 0 && channel < channelsToOutput)
                buffer[channel + 1] = value;
        }

        /* setStartCode(byte value)
         * sets the start code sent out to the device
         */
        public static void setStartCode(byte value)
        {
            buffer[0] = 0;
        }

        /* setNumChannels(int channels)
         * sets the number of channels in the universe
         * valid inputs are 1-512 inclusive
         */
        private static void setNumChannels(int channels)
        {
            if (channels > 512 || channels < 1)
            {
                return;
            }
            channelsToOutput = channels;
            buffer = new byte[channelsToOutput + 1];
        }

        /* private writeData()
         * function for thread to interface to adapter
         */
        private static void writeData()
        {
            while (!done)
            {
                initOpenDMX();
                FT_SetBreakOn(handle);
                FT_SetBreakOff(handle);
                bytesWritten = write(handle, buffer, channelsToOutput + 1);
                Thread.Sleep(25);
            }
        }

        /* private write(uint handle, byte[] data, int length)
         * function called by writeData()
         */
        private static int write(uint handle, byte[] data, int length)
        {
            IntPtr ptr = Marshal.AllocHGlobal((int)length);
            Marshal.Copy(data, 0, ptr, (int)length);
            uint bytesWritten = 0;
            status = FT_Write(handle, ptr, (uint)length, ref bytesWritten);
            Marshal.FreeHGlobal(ptr);
            return (int)bytesWritten;
        }

        /* initOpenDMX()
         * sets up DMX adapter, must be called after open.
         */
        public static void initOpenDMX()
        {
            status = FT_ResetDevice(handle);
            status = FT_SetDivisor(handle, (char)12);  // set baud rate
            status = FT_SetDataCharacteristics(handle, BITS_8, STOP_BITS_2, PARITY_NONE);
            status = FT_SetFlowControl(handle, (char)FLOW_NONE, 0, 0);
            status = FT_ClrRts(handle);
            status = FT_Purge(handle, PURGE_TX);
            status = FT_Purge(handle, PURGE_RX);
        }

    }

    /// <summary>
    /// Enumaration containing the varios return status for the DLL functions.
    /// </summary>
    public enum FT_STATUS
    {
        FT_OK = 0,
        FT_INVALID_HANDLE,
        FT_DEVICE_NOT_FOUND,
        FT_DEVICE_NOT_OPENED,
        FT_IO_ERROR,
        FT_INSUFFICIENT_RESOURCES,
        FT_INVALID_PARAMETER,
        FT_INVALID_BAUD_RATE,
        FT_DEVICE_NOT_OPENED_FOR_ERASE,
        FT_DEVICE_NOT_OPENED_FOR_WRITE,
        FT_FAILED_TO_WRITE_DEVICE,
        FT_EEPROM_READ_FAILED,
        FT_EEPROM_WRITE_FAILED,
        FT_EEPROM_ERASE_FAILED,
        FT_EEPROM_NOT_PRESENT,
        FT_EEPROM_NOT_PROGRAMMED,
        FT_INVALID_ARGS,
        FT_OTHER_ERROR
    };

    /*   struct FT_DEVICE_LIST_INFO_NODE 
       { 
           DWORD Flags; 
           DWORD Type; 
           DWORD ID; 
           DWORD LocId; 
           char SerialNumber[16]; 
           char Description[64]; 
           FT_HANDLE ftHandle; 
       } ;*/
}
